Saturday, March 16, 2013

Задача 3: Выбор и подсветка объектов в Unity3d

Практическое использование движка Unity3d.




Задача 3: Выбор и подсветка объектов в Unity3d


Содержание

  • Задача 3: Выбор и подсветка объектов в Unity3d.
  • 1. Выбор объекта 
  • 2. Подсветка объекта 
  • Замечания



Задача 3: Выбор и подсветка объектов в Unity3d.

  В этой статье рассмотрим следующую задачу: необходимо реализовать выбор объектов левой клавишей мыши, причем при выборе объекта необходимо его подсветить. Эта статья является продолжением второй статьи (http://denis-potapenko.blogspot.com/2013/03/2-unity3d.html). Поэтому перед началом чтения этой статьи желательно прочесть вторую часть. Результат, который мы хотим получить представлен на рисунке В:
Рисунок. В. – Результирующая сцена



Выбор объекта

  Для начала создадим переменную – член класса, которая будет хранить выбранный объект. 
// selected GameObject
private GameObject mSelectedObject;
  Также создадим свойство на этот объект.

/// 
/// Gets or sets selected GameObject
/// 
public GameObject SelectedObject
{
    get
    {
        return mSelectedObject;
    }
    set
    {
        …

        // assign new game object
        mSelectedObject = value;

        …
    }
}


  Работу сеттера этого свойства рассмотрим ниже.
  Теперь создадим метод, который будет выбирать объект. Определим объект, находящийся под мышкой. Алгоритм выбора объекта следующий:
  • Перевести позицию мышки из пространства окна (2д координаты) в трехмерные координаты (А);
  • Построить луч, идущий от позиции мышки в 3д координатах в сторону «от камеры» (Б);
  • Найти пересечение луча с любым объектом в сцене;
  • Первый объект, попавшийся на пересечении, является выбранным;


  Преобразуем позицию мыши в луч исходящий от камеры (Объединим пункты А и Б) используя метод «ScreenPointToRay».
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  Теперь найдем пересечение луча с любым объектом в сцене. Для этого используем метод «Physics.Raycast». 
RaycastHit hit;
if (Physics.Raycast(ray, out hit, Constants.cMaxRayCastDistance))
  Хочу сразу заметить, что на каждый объект, с которым мы хотим посчитать пересечение, должен быть назначен так называемый «Collider». «Collider» используется в основном для работы с физикой и в частности для определения пересечений. Для того чтоб назначить «Collider» на GameObject, необходимо выделить объект в сцене, и выбрать пункт меню «Component / Physics / Box Collider». В дальнейших статьях мы более подробно изучим эти компоненты.
  В случае если пересечение найдено метод «Physics.Raycast» возвращает «true» и заполняет переменную «hit». Теперь определим GameObject, который соответствует найденному объекту типа «RaycastHit». 

// get game object
GameObject rayCastedGO = hit.collider.gameObject;

И наконец, установим найденный объект, как выделенный.
// select object
this.SelectedObject = rayCastedGO;
  А теперь нужно вызвать метод “SelectObjectByMousePos” в методе «Update». Причем вызывать мы его будет, только при нажатии левой клавиши мыши.
// process object selection
if (Input.GetMouseButtonDown(0))
{
    SelectObjectByMousePos();
}
  Теперь давайте подробно рассмотрим, как работает сеттер свойства «SelectedObject».


Подсветка объекта

  С начала немного теории. Дело в том, что подсветку объектов можно сделать множеством способов. Я расскажу способ, который можно добиться, не используя никакие дополнения к движку Unity3d. После описания теории и практики я расскажу Вам, на мой взгляд, более совершенный способ реализации подсветки.
  И так алгоритм реализации подсветки объектов следующий:
  • Создать 2 материала: первый обычный материал, второй материал подсветки;
  • Передать эти материалы в скрипт;
  • При выделении объекта, установить «подсвеченный» материал на выделенный объект;
  • На объект, который был выделен ранее, установить обычный материал;
Теперь рассмотрим реализацию этого метода. В редакторе Unity3d создадим папку с материалами, и назовём ее «Materials». В этой папке создадим 2 материала – обычный “SimpleMat”, и подсвеченный “HighlightedMat”. Получившая иерархия папок и файлов показана на рисунке 2.1

Рис. 2.1 - Структура файлов и папок с материалами

  Хочу отметить, что у обоих материалов установлен стандартный шейдер “Diffuse”. Материалы настроены идентично, за исключением того что у материала «HighlightedMat» диффузный цвет установлен в красный. На рис. 2.2 показаны настройки материала «HighlightedMat».

Рисунок 2.2 – Настройки материала «HighlightedMat»

  Теперь создадим 2 публичных переменных – члена класса “AppRoot”, которые будут хранить созданные материалы.
// materials for highlight
public Material SimpleMat;
public Material HighlightedMat;

Теперь присвоим этим переменным значение. Сделаем это прямо в редакторе. Процесс назначения материалов в скрипт показан на рис. 2.3.

Рисунок 2.3 – Назначение материалов в скрипт “AppRoot”

  Теперь наконец-то реализация сеттера свойства “SelectedObject”.
  Получим «старый» выбранный объект.
// get old game object
GameObject goOld = mSelectedObject;

Установим новый выбранный объект.
// assign new game object
mSelectedObject = value;
Если выбранный объект не поменялся, просто выходим с метода.

// if this object is the same - just not process this
if (goOld == mSelectedObject)
{
    return;
}

  Установим материал на старый выбранный объект.
// set material to non-selected object
if (goOld != null)
{
    goOld.renderer.material = SimpleMat;
}

  Установим материал на выбранный объект.
// set material to selected object
if (mSelectedObject != null)
{
    mSelectedObject.renderer.material = HighlightedMat;
}

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

Замечания

  Как я уже писал выше, я не считаю выше описанный алгоритм подсветки объектов самым эффективным. Я считаю, что более эффективный метод будет использовать шейдер с компонентом «Emissive». Этот компонент умножается особым способ на результирующий цвет, таким образом, выделенный объект получает «окраску» например красного цвета, а не полностью закрашивается. Вы вполне можете создать такой шейдер своими руками. В последующих статьях я расскажу как это сделать. Из существующих стандартных шейдеров в Unity3d, я нашел шейдер «Reflective/Diffuse», который в некоторых случаях очень подходит для целей подсветки объектов. Можете попробовать использовать этот шейдер и его поле “Reflection Color” вместе “Emissive”.

Демонстрацию работы можно посмотреть здесь:
https://dl.dropbox.com/u/20023505/Articles/Unity3d/Lesson3/WebPlayer/WebPlayer.html

Исходные коды Вы как всегда сможете бесплатно скачать здесь:
https://dl.dropbox.com/u/20023505/Articles/Unity3d/Lesson3/Sources/ Sources.zip
https://github.com/den-potapenko/Unity3dArticles/tree/master/Lesson3

3 comments:

  1. Не получается у меня выделить объект от First Person который я создал из fbx.
    работает при строчке
    Ray ray = new Ray(transform.position, transform.forward);
    Но тогда надо стоять напротив объекта
    А при строчке
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    Не находит объект
    Что можно сделать?

    ReplyDelete
  2. Пожалуйста дайте ссылку, где можно скачать Ваш проект, и я помогу Вам

    ReplyDelete
  3. Как по-мне, то проще воспользоваться событиями наводки/отводки мыши на объект.
    public class Target : MonoBehaviour {
    Material defaultMat;
    Material newMat;
    void Start() { defaultMat = transform.renderer.material; }
    void OnMouseEnter() { transform.renderer.material = newMat; }
    void OnMouseExit() { transform.renderer.material = defaultMat; }
    }

    ReplyDelete