Всем привет. Представляю вашему вниманию перевод статьи из блога, посвященному разработке на Unity — The Knights of Unity. В ней пойдет речь о создании миникарты. Я также реализовал описанное в статье и выложил это на Битбакете.


Что требуется для создания миникарты в игре на Unity? Наверно вы будете удивлены, но это проще, чем вы думаете и даже не требует навыков программирования! Далее я постараюсь шаг за шагом объяснить, как это сделать.


Основные идеи миникарты



Миникарты (или радары) предназначены для отображения информации о том, что нас окружает. В первую очередь, миникарта должна быть отцентрирована по главному персонажу. Затем, вместо реальных моделей на ней нужно использовать удобочитаемые обозначения, поскольку миникарты зачастую довольно небольшие, и игрок может не распознать информацию, которую миникарта пытается предоставить.

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

Подготовка сцены


Начнем с добавления кое-чего на сцену. Я создал сцену с Unity-тян (она будет игроком) и двумя роботами (которые будут противниками).


Вид игры

Теперь добавим вторую камеру. Просто выберите пункт главного меню GameObject -> Camera и переименуйте созданную камеру как Minimap Camera. Теперь сделайте эту камеру дочерним объектом по отношению к Unity-тян (это позволит камере следовать за ней) и поднимите ее на 10 единиц выше головы Unity-тян, повернув при этом вниз.


Настройка камеры миникарты

Для получения хорошего результата установите позицию компонента Transform в 0, 10, 0 и поворот в 90, 0, 0. Камера должна показывать приблизительно вот это:



Хорошо, но это еще не миникарта. Если вы сейчас запустите сцену, изображение с камеры будет просто отображаться на экране. Мы должны сообщить игре, что хотим представить миникарту как UI-элемент.

Отображение в UI-элемент


Для этого нам потребуется Render Texture. Вы можете легко создать ее, выбрав пункт главного меню Assets -> Create -> Render Texture. Создайте и переименуйте как Minimap Render Texture.



Теперь выберите камеру Minimap Camera и назначьте в инспекторе полю Target Texture созданную ранее Minimap Render Texture.



Если вы попробуете запустить сцену, то заметите, что нигде не видно изображения с камеры Minimap Camera. Изображение теперь скрыто в созданной Minimap Render Texture.

Давайте теперь создадим Canvas для добавления на него UI-элементов. Выберите пункт меню GameObject -> UI -> Canvas и Canvas появится на сцене.



На Canvas нужно добавить объект Raw Image, чтобы с его помощью использовать Render Texture. Выберите пункт меню GameObject -> UI -> Raw Image, переименуйте созданный объект как Minimap Image и назначьте в инспекторе полю Texture нашу Minimap Render Texture.



В результате у нас получился UI-элемент, который отображает изображение с камеры Minimap Camera!



Сделаем его в виде окружности. Для этой цели я приготовил простую текстуру-маску:



Создайте новый UI-элемент Image, добавьте на него компонент Mask, в инспекторе задайте полю Source Image нашу текстуру-маску и сделайте объект Minimap Image дочерним по отношению к Mask (подсказка: отключите Mipmaps для текcтуры-маски для лучшего визуального эффекта).



После этих действий наша миникарта будет выглядеть вот так:



Белая миникарта на белом фоне выглядит не очень хорошо. Давайте добавим изображение с контуром поверх нее:





Чтобы было проще перемещать всю эту конструкцию, я сгруппировал ее в отдельный пустой объект, назвав его Minimap.



Наконец, давайте переместим созданную миникарту в верхний правый угол экрана.



Выглядит хорошо, неправда ли? Но это все еще ненастоящая миникарта — это камера, отображающая игру сверху. Если вы знакомы со слоями, то вероятно догадываетесь, что я сделаю далее!

Развлекаемся со слоями


Нам нужен по крайней мере один дополнительный слой. Перейдите в меню Edit -> Project Settings -> Tags and Layers и добавьте новый слой Minimap.



Теперь создадим три сферы. Одну синего цвета, расположенную в позиции, где стоит Unity-тян. Наилучшим способом будет сделать сферу дочерней по отношению к Unity-тян. Убедитесь, что слой сферы установлен как Minimap.



Проделайте похожие действия с роботами, только вместо синих сфер используйте красные.



Теперь самая лучшая часть! Выберите камеру Main Camera и убедитесь, что ее свойство Culling Mask не содержит отмеченным слой Minimap.



Теперь выберите камеру Minimap Camera и сделайте с ней обратное действие. Отставьте отмеченным только слой Minimap и отключите все остальные.



Теперь вы видите что-то похожее на настоящую миникарту!



Последние штрихи


Возможно вы захотите внести несколько корректировок. Прежде всего, давайте изменим цвет отсечения камеры Minimap Camera на светло-серый и установим свойство Clear Flags как Solid Color, чтобы фон миникарты лучше контрастировал с синей и красными сферами.



Теперь вы можете добавить на миникарту любые другие UI-элементы. Для примера я добавил текст. И вот конечный результат!



Миникарта создана так, чтобы немедленно обновлять свою позицию при перемещении персонажа. Если кто-то из роботов тоже переместится, это будет также отображено.



И это конец урока по миникарте!
Поделиться с друзьями
-->

Комментарии (20)


  1. Lertmind
    04.07.2016 00:39
    +4

    Другой способ: отображать заранее отрендеренный ландшафт как фон и спрайты в UI для обозначения предметов. Это конечно потребует написание кода, зато предполагаю будет меньше вычислений.


  1. jonic
    04.07.2016 01:25
    +18

    По моему, это все равно что забивать гвозди микроскопом… Рендер сцены, второй раз… Я теперь понимаю почему плохие игры на юнити тормозят.


    1. Rathil
      04.07.2016 01:50

      Я не спец в Unity2D, но хотелось бы узнать, а как правильно это сделать с вашей т. зрения?


      1. Ockonal
        04.07.2016 03:27
        +4

        Есть координаты любых тел. Можно отбросить глубину и взять только x, y (если сверху смотреть). Потом делается масштаб и рисуются обычные спрайты кружочки или чего там еще.


    1. Ichimitsu
      04.07.2016 07:16
      +4

      Хуже будет дальше, когда они начнут делать масштаб и пытаться зарендерить все кружочки попавшие в камеру и весь мир))), а еще попытаться сохранить масштаб кружочка персонажа, чтобы при увеличении карты он не стал размером с пиксель.


      1. Charoplet
        04.07.2016 12:27

        Зачем весь мир реалтайм на миникарте рендерить? А про масштаб проблемы не вижу: делаем камеру ортогональной и меняем ее Size как нужно.


    1. Charoplet
      04.07.2016 12:20

      Рендерится один только слой. Я попробовал закинуть на сцену побольше роботов (30) и посмотреть изменение фпс… не увидел просадки. Точнее, просадка была, но не из-за миникарты, а из-за бОльшего количества образовавшихся теней от объектов, что неудивительно. После оключения теней разницы в фпс не вижу.


  1. Smi1e
    04.07.2016 01:28
    +3

    Быть может глупый вопрос, но разве дополнительный рендер сцены не влечет кратное повышение ресурсоемкости? Да еще эти фокусы с шариками. Похоже на большой колхоз «на коленке» с инструментами, не предназначенными для таких задач в принципе.


    1. Tutanhomon
      04.07.2016 10:16
      +1

      Рендерится только слой с шариками и фон, так что ответ на Ваш вопрос скорее «нет», чем «да». Однако на «колхоз» действительно похоже. Не оптимально, не производительно, но зато быстро. Так что как вариант — имеет право на жизнь.


    1. Charoplet
      04.07.2016 12:22
      -3

      Ответил выше про рендер. А какой инструмент вас смутил? Render Texture? Читаем мануал юнити: Render Textures are special types of Textures that are created and updated at runtime.


  1. Vilyx
    04.07.2016 02:00
    +4

    За такие методы рисования миникарты стоит выделять отдельный котёл. Как сделать грамотно: текстура фона(ортографический рендер или рисованная карта), поверх неё текстуры обозначающие персонажей, дальше понадобится немного кода, который будет мировые координаты персонажей приводить к экранным координатам относительно фона.
    А описанный в статье метод, даёт лишние дроуколлы, которые могут понадобиться для чего-то более красивого.


    1. Charoplet
      04.07.2016 12:23
      -2

      Ваш же метод тоже даст лишние дроуколлы


      1. Vilyx
        04.07.2016 12:47
        +1

        Один дроуколл на всё, если только не используется какой-нибудь iGUI.


  1. k12th
    04.07.2016 11:53
    +2

    На этапе прототипирования, когда геймдиз срочно просит миникарту, может пригодиться.


  1. Akuma
    04.07.2016 12:15
    +1

    Ух, помню еще на Blitz3D делал такую «мини-карту» и просто надеялся, что оно не будет тормозить.
    Прошло столько времени, уже и движек другой, а колхозные методы остались. Приятно :)

    Конечно, данный способ не применим в продакшене, но, как и сказали выше, когда срочно требуется составить план мира, как временный вариант — самое то, даже без кружочков.


  1. streeter12
    04.07.2016 14:51
    +1

    Данный метод несмотря на свою простоту имеет явные недостатки.
    1. Низкая производительность.
    2. Для добавления новых меток надо создавать новые сферы (лишний хлам в префабах).
    3. Добавление новых типов меток и их фильтров для различных игроков сильно затруднено.
    4. Для смены внешнего вида метки необходимо создавать меш!
    5. Лишние объекты на каждой сцене => лишняя сложность => сложнее разработка и поддержка.
    6. Сложно тестировать => больше возможных багов (с учетом 3).

    По моему мнению нужно сразу избегать подобных решений.
    Как вариант делаем Mono скрипт MiniMap с возможностью добавления и удаления Mono объектов типа MapMarker.
    MiniMap отслеживает координаты MapMarker`s и основываясь на внутренней логике возвращает относительные координаты объектов.
    За прорисовку карты отвечает третий скрипт который не входит в систему мини карты (GUI отделено от логики).

    Более подробного описания сделать сложно. Точный вид классов сильно зависит от задачи решаемых разработчиком.


  1. Rathil
    04.07.2016 17:47

    Вопрос не по теме, но: можно ли сделать так, чтобы один и тот же объект для разных камер имел разный цвет? Как-то через шейдеры? Основное условие — не создавать 2 объекта, а использовать один объект.


    1. Vilyx
      04.07.2016 18:08

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

      using UnityEngine;
      using System.Collections;
      
      public class PrerenderChangeMaterial : MonoBehaviour {
      
      	public Material replacementMat;
      	public GameObject target;
      
      	protected Material tempMaterial;
      
      	protected MeshRenderer targetRenderer;
      
      	void Awake()
      	{
      		targetRenderer = target.GetComponent<MeshRenderer>();
      	}
      	void OnPreRender()
      	{
      		tempMaterial = targetRenderer.sharedMaterial;
      		targetRenderer.sharedMaterial = replacementMat;
      	}
      	void OnPostRender()
      	{
      		targetRenderer.sharedMaterial = tempMaterial;
      	}
      }
      

      Только вешать его надо на камеру, на которой надо подменить материал. Ещё есть вариант сделать с Camera.SetReplacementShader, но там всё немного сложнее и вряд ли решит именно вашу задачу. И ещё учтите, что этот код изменит материал у всех объектов, на которых висит тот же материал, что и на целевом объекте, если вам это не нужно, то замените sharedMaterial на material.


      1. Vilyx
        04.07.2016 18:15

        Извиняюсь, двусмысленно написал. Я хотел сказать, что если изменить свойства sharedMaterial, то это отразится на всех объектах с тем же материалом. В приведённом коде всё будет работать нормально, т.е. материал сменится только у целевого объекта.


      1. Rathil
        04.07.2016 19:11

        Спасибо, попробую Ваш подход. Выглядит именно так, как мне нужно.