Saturday, March 16, 2013

Task #3: Object selection and highlight in Unity3d

Practical use of Unity3d engine.



Task #3: Objects selection and highlight in Unity3d

Contents

  • Task #3: Objects selection and highlight in Unity3d.
  • 1. Object selection
  • 2. Object highlight
  • Remarks


Task #3: Objects selection and highlight in Unity3d.

  This article will show solution for the task: application should implement object selection by left mouse button down. Also application should highlight selected object. This article is base on the second one (http://denis-potapenko.blogspot.com/2013/03/task-2-rendering-hotspots-in-unity3d.html). So, I suggest reading the second article first. Figure B shows result we want to achieve:
Figure В. – Result scene

Object selection

  At first, create class variable, which will store the selected object.
// selected GameObject
private GameObject mSelectedObject;

  Also, let’s create property for this object.
/// 
/// Gets or sets selected GameObject
/// 
public GameObject SelectedObject
{
    get
    {
        return mSelectedObject;
    }
    set
    {
        ...

        // assign new game object
        mSelectedObject = value;

        ...
    }
}
  Full implementation of this property will be described below.
  And now, create method, which implementation is object selection. We need to determine what object is located under the mouse. Object selection pipeline is:

  • Convert mouse position from screen space to three-dimensional space (A);
  • Build ray, directed from mouse position to “camera forward” way (B);
  • Find intersection of ray and any object in the scene;
  • Find the first object that occurred on intersection; 

  Convert mouse position to ray that spreads to “camera forward” (Combine A and B) using «ScreenPointToRay» method.
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  Now, let’s find ray intersection with any object in scene. To implement this call function «Physics.Raycast».

RaycastHit hit;
if (Physics.Raycast(ray, out hit, Constants.cMaxRayCastDistance))

  I should notice, that each object we want to get intersection should have «Collider» component. Colliders mainly used in physics, particularly to determine object intersection. To assign «Collider» to GameObject you need to select the object in the scene and choose menu item «Component / Physics / Box Collider». In future articles we will have more detailed description of this component.
  So, if intersection exist, function «Physics.Raycast» returns «true» and sets «hit» variable. Now, let’s determine intersected GameObject. 
// get game object
GameObject rayCastedGO = hit.collider.gameObject;

  And finally, set this object as selected.
// select object
this.SelectedObject = rayCastedGO;
  And now, we need to call function “SelectObjectByMousePos” in «Update» one. But we will call it only when left mouse button is pressed.
// process object selection
if (Input.GetMouseButtonDown(0))
{
    SelectObjectByMousePos();
}
  And now, let’s take a look to «SelectedObject» function implementation.

Object highlight

  At first, I’ll tell a bit of theory. Object highlight we can do by a lot of ways. I will describe a way that we can achieve without any add-ons to Unity3d engine. But after that, I will describe you a better way of highlight implementation, but it will require doing some special actions. 
  So highlight implementation is divided to such parts:
  • Create two materials: the first one is simple material, and the second material is used for highlight;
  • Pass these materials to script;
  • When object is selected set its material to highlighted;
  • To object that was selected before, set simple material;
  Now, let’s take a look to implementation. In Unity3d editor create folder with materials and name it «Materials». In this folder create two materials: simple “SimpleMat” and highlighted “HighlightedMat”. Result folder tree is shown on figure 2.1.

Figure 2.1 – Files and folders structure with created materials

  I want to notice that “Diffuse” shader is set to both materials. Materials are configured identically, except that the material «HighlightedMat» has diffuse color set to red. Material settings of «HighlightedMat» are shown on figure 2.2.
Figure 2.2 – Settings of «HighlightedMat» material

  Now, create two public class variables in “AppRoot” class, which will contain materials data.
// materials for highlight
public Material SimpleMat;
public Material HighlightedMat;
  Set value to this variables. We can do it directly in the editor. Process of assigning values to materials is shown on figure 2.3.
Figure 2.3 – Process of assigning materials to the script "AppRoot" 


  So, finally let’s have a look to implementation of the setter of the property “SelectedObject”. 
Get «old» selected object.

// get old game object
GameObject goOld = mSelectedObject;
  Set new selected object.
// assign new game object
mSelectedObject = value;
  If selected object is not changed, just leave this function.
// if this object is the same - just not process this
if (goOld == mSelectedObject)
{
    return;
}
  Set material to old selected object.
// set material to non-selected object
if (goOld != null)
{
    goOld.renderer.material = SimpleMat;
}
  Set material to selected object.
// set material to selected object
if (mSelectedObject != null)
{
    mSelectedObject.renderer.material = HighlightedMat;
}
  As a result, we have got a scene, in which user by means of left mouse button can select objects and that object will be highlighted to red color. 

Remarks

  As I mentioned, I don’t think that described above way is the best way to highlight the object. I consider that the more efficient and flexible way to highlight the object is to use shader with «Emissive» component. This component is multiplied to result color by special way, so the object gets only “coloring” of red color but not the full red color. You can do such shader yourself, and in further articles I will tell you how to do it. From existing set of Unity3d shaders I found one that can fit to highlight purpose in some situations. It’s named «Reflective/Diffuse». You can use this shader and its field “Reflection Color” instead of “Emissive”.


Results of development you can see here:
https://dl.dropbox.com/u/20023505/Articles/Unity3d/Lesson3/WebPlayer/WebPlayer.html

As always for free you can download source codes from here:
https://dl.dropbox.com/u/20023505/Articles/Unity3d/Lesson3/Sources/Sources.zip
https://github.com/den-potapenko/Unity3dArticles/tree/master/Lesson3

Задача 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

Saturday, March 2, 2013

Task #2: Rendering hotspots in Unity3d


Practical use of Unity3d engine.




Task #2: Rendering hotspots in Unity3d


Contents

  • Task 2: Rendering hotspots in Unity3d. 
  • Get rooms list 
  • Rendering hotspots


Task 2: Rendering hotspots in Unity3d.

In this article we will solve next task: we need to display hotspots for rooms. This article is base on first one (http://denis-potapenko.blogspot.com/2013/02/task-1-rotate-pan-and-zoom.html). So, I strongly suggest reading the first article. As result, we want to achieve is shown on Figure:

Figure B.


Get rooms list

To get list of rooms, at first, create an array of rooms’ names.
private string[] mGORoomsNames = new string[]
{
    "Room0",
    "Room1",
    "Room2"
};

Names of rooms we can get from «Hierarchy» window shown in Fig. 1.1.

  

Figure 1.1. – Rooms’ names

Also, let’s create variable containing list of rooms’ GameObjects.
private List<GameObject> mGORooms = new List<GameObject>();
In «Start» function, get rooms’ GameObjects.
foreach (var item in mGORoomsNames)
{
    GameObject goRoom = GameObject.Find(item);
    mGORooms.Add(goRoom);
}

Rendering hotspots

Create constants for hotspots’ width and height.
private const float cHotspotSizeX = 70;
private const float cHotspotSizeY = 24;
And now, create OnGUI function. Unity3d uses this function for rendering UI. At the start of this function create temporary variable, which will store current hotspot position.
Rect tmpRect = new Rect();
Although, for clarity, I instantiated class “Rect” right in “OnGUI” function, I highly recommend for such purpose to instantiate class variables immediately in class declaration. The reason is that the application will instantiate the class every time when “OnGUI” function called. In theory, the garbage collector will release the allocated memory, but, at first, I have seen when garbage collector for some reason doesn’t do it, and secondly, because memory fragmentation is not very good.
Next, let’s determine rooms’ positions in three-dimensional space.
// get position of room in 3d space
Vector3 roomPos = goRoom.transform.position;
Now, let’s determine position of this object on the screen. To do this convert three-dimensional coordinate to screen coordinates (two-dimensional coordinate).
// convert room position from 3d space to screen space (2d)
Vector3 hotSpotPos = Camera.mainCamera.WorldToScreenPoint(roomPos);
Also, we need to calculate rectangle for hotspot rendering. Calculation is very simple: position – it’s object’s position minus size of hotspot.
// calculate rect for rendering label
tmpRect.x = hotSpotPos.x - cHotspotSizeX / 2;
tmpRect.y = Screen.height - hotSpotPos.y - cHotspotSizeY / 2;
tmpRect.width = cHotspotSizeX;
tmpRect.height = cHotspotSizeY;
Finally, render hotspot:
// now render label at this point
GUI.Box(tmpRect, mGORoomsNames[i]);
I should mention, that hotspot will be rendered it proper position even when object rotates, pans or zooms. 
Results of development you can see here:
https://dl.dropbox.com/u/20023505/Articles/Unity3d/Lesson2/WebPlayer/WebPlayer.html

Source code you can download from here:
https://dl.dropbox.com/u/20023505/Articles/Unity3d/Lesson2/Sources/Sources.zip
https://github.com/den-potapenko/Unity3dArticles/tree/master/Lesson2

Задача 2: Отображение «хотспотов» для объекта в Unity3d


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





Задача 2: Отображение «хотспотов» для объекта в Unity3d



Содержание


  • Задача 2: Отображение «хотспотов» для объекта в Unity3d.
  • Получение списка комнат
  • Отображение хотспотов

Задача 2: Отображение «хотспотов» для объекта в Unity3d.

 Теперь давайте рассмотрим следующую задачу: необходимо продемонстрировать «хотспоты» для комнат. Эта статья является продолжение работы первой статьи.
http://denis-potapenko.blogspot.com/2013/02/unity3d_407.html 
 Поэтому перед началом этой статьи желательно прочесть первую. В результате мы должны получить следующее:
Рис. В.

Получение списка комнат

Для получение списка комнат создадим массив названий комнат.
 private string[] mGORoomsNames = new string[] 
    {
        "Room0",
        "Room1",
        "Room2"
    };
Названия комнат мы можем получить, посмотрев в окно «Hierarchy» показанное на рис. 1.1.
Рисунок 1.1. – Список названий комнат


Также создадим переменную, которая будет в себе содержать список GameObject’ов комнат.

private List < GameObject > mGORooms = new List < GameObject >();

В методе «Start» получим GameObject’ы комнат.
foreach (var item in mGORoomsNames)
      {
          GameObject goRoom = GameObject.Find(item);
          mGORooms.Add(goRoom);
      }

Отображение хотспотов

 Создадим константы, которые будут хранить ширину и высоту хотспота.

private const float cHotspotSizeX = 70;
private const float cHotspotSizeY = 24;

 А теперь создадим функцию OnGUI, с помощью которой Unity3d будет отображать UI. В начале этой функции создадим временную переменную, которая будет хранить положение текущего хотспота.

Rect tmpRect = new Rect();

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

// get position of room in 3d space
Vector3 roomPos = goRoom.transform.position;

Теперь определим позицию этого же объекта на экране. Для этого конвертируем трехмерную координату в координаты окна (в двухмерные координаты).
// convert room position from 3d space to screen space (2d)
Vector3 hotSpotPos = Camera.mainCamera.WorldToScreenPoint(roomPos);
Также необходимо вычислить прямоугольник для отображение текущего хотспота. Вычисление очень простое: позиция – это позиция объекта минус половина от размера хотспота.
// calculate rect for rendering label
tmpRect.x = hotSpotPos.x - cHotspotSizeX / 2;
tmpRect.y = Screen.height - hotSpotPos.y - cHotspotSizeY / 2;
tmpRect.width = cHotspotSizeX;
tmpRect.height = cHotspotSizeY;
Наконец, отображаем хотспот.
// now render label at this point
GUI.Box(tmpRect, mGORoomsNames[i]);
Хочу отметить, что даже при вращении, перемещении и масштабировании объектов, хотспоты останутся соответствующих местах.

Посмотреть результаты работы можно посмотреть здесь:
https://dl.dropbox.com/u/20023505/Articles/Unity3d/Lesson2/WebPlayer/WebPlayer.html


Исходный код здесь:

https://dl.dropbox.com/u/20023505/Articles/Unity3d/Lesson2/Sources/Sources.zip
https://github.com/den-potapenko/Unity3dArticles/tree/master/Lesson2